package com.orange.core.service.receipt;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.orange.core.mapper.receipt.ReceiptMapper;
import com.orange.core.mapper.shop.ShopMapper;
import com.orange.core.mapper.shopCategory.ShopCategoryMapper;
import com.orange.core.mapper.user.UserMapper;
import com.orange.core.pojo.PageReqDto;
import com.orange.core.pojo.entity.credit.CreditFlow;
import com.orange.core.pojo.entity.receipt.Receipt;
import com.orange.core.pojo.entity.receipt.ReceiptPic;
import com.orange.core.pojo.entity.shop.Shop;
import com.orange.core.pojo.entity.shopCategory.ShopCategory;
import com.orange.core.pojo.entity.user.User;
import com.orange.core.pojo.enums.EAudit;
import com.orange.core.pojo.enums.EFlowBusinessType;
import com.orange.core.pojo.enums.EFlowType;
import com.orange.core.pojo.req.receipt.ReceiptAuditReq;
import com.orange.core.pojo.req.receipt.ReceiptPageReq;
import com.orange.core.pojo.req.receipt.ReceiptSaveReq;
import com.orange.core.service.credit.CreditFlowService;
import com.orange.core.util.PrincipalUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class ReceiptService extends ServiceImpl<ReceiptMapper, Receipt> {

    private static final String USER_RECEIPT_UPLOAD_COUNT_PREFIX = "receipt:";

    private final ReceiptPicService receiptPicService;
    private final CreditFlowService creditFlowService;
    private final UserMapper userMapper;
    private final ShopMapper shopMapper;
    private final ShopCategoryMapper shopCategoryMapper;
    private final StringRedisTemplate redisTemplate;

    public Page<Receipt> page(PageReqDto<ReceiptPageReq> req) {
        ReceiptPageReq condition = req.getCondition();
        Page<Receipt> page = this.lambdaQuery()
                .eq(condition.getUserId() != null, Receipt::getUserId, condition.getUserId())
                .eq(condition.getAuditStatus() != null, Receipt::getAuditStatus, condition.getAuditStatus())
                .ge(condition.getStartTime() != null, Receipt::getCreateTime, condition.getStartTime())
                .le(condition.getEndTime() != null, Receipt::getCreateTime, condition.getEndTime())
                .orderByDesc(Receipt::getId)
                .page(req.toPage());
        List<Receipt> records = page.getRecords();
        if (CollUtil.isNotEmpty(records)) {
            List<Integer> receiptIds = records.stream().map(Receipt::getId).toList();
            Map<Integer, List<ReceiptPic>> receiptPicMap = receiptPicService.lambdaQuery()
                    .in(ReceiptPic::getReceiptId, receiptIds)
                    .list()
                    .stream().collect(Collectors.groupingBy(ReceiptPic::getReceiptId));

            List<Integer> userIds = records.stream().map(Receipt::getUserId).toList();
            Map<Integer, User> userMap = userMapper.selectBatchIds(userIds)
                    .stream().collect(Collectors.toMap(User::getId, Function.identity()));

            List<Integer> shopIds = records.stream().map(Receipt::getShopId).toList();
            Map<Integer, String> shopMap = shopMapper.selectBatchIds(shopIds)
                    .stream().collect(Collectors.toMap(Shop::getId, Shop::getName));
            for (Receipt record : records) {
                record.setNickname(userMap.get(record.getUserId()).getNickname());
                record.setAvatar(userMap.get(record.getUserId()).getAvatar());
                record.setPics(receiptPicMap.get(record.getId()));
                record.setShopName(shopMap.get(record.getShopId()));
            }
        }
        return page;
    }

    public Receipt info(int id) {
        Receipt receipt = this.getById(id);
        Assert.notNull(receipt, "小票不存在");
        List<ReceiptPic> receiptPics = receiptPicService.lambdaQuery()
                .eq(ReceiptPic::getReceiptId, id)
                .list();
        receipt.setPics(receiptPics);
        return receipt;
    }

    /**
     * 上传小票
     */
    @Transactional
    public void save(ReceiptSaveReq req) {
        String count = redisTemplate.opsForValue().get(USER_RECEIPT_UPLOAD_COUNT_PREFIX + PrincipalUtil.getId());
        if (StrUtil.isNotBlank(count)) {
            Assert.isTrue(Integer.parseInt(count) < 10, "每天只能上传10张小票");
        }
        Shop shop = shopMapper.selectById(req.getShopId());
        Float rate = shop.getPointsRatio();
        if (rate == null || rate == 0F) {
            ShopCategory shopCategory = shopCategoryMapper.selectById(shop.getCategoryId());
            rate = shopCategory.getPointsRatio();
        }
        Assert.notNull(rate, "尚未配置积分兑换比率，请联系管理员");

        // 计算预计积分，使用四舍五入
        int evaluateCredit = Math.round(req.getAmount().floatValue() * rate);

        Receipt receipt = new Receipt()
                .setUserId(PrincipalUtil.getId())
                .setShopId(req.getShopId())
                .setAmount(req.getAmount())
                .setEvaluateCredit(evaluateCredit)
                .setCreditRatio(rate);
        this.save(receipt);

        receiptPicService.saveBatch(req.getPicUrls().stream()
                .map(p -> new ReceiptPic()
                        .setReceiptId(receipt.getId())
                        .setPicUrl(p))
                .toList());

        redisTemplate.opsForValue().increment(USER_RECEIPT_UPLOAD_COUNT_PREFIX + PrincipalUtil.getId());
        long between = DateUtil.endOfDay(new Date()).between(new Date(), DateUnit.SECOND);
        redisTemplate.expire(USER_RECEIPT_UPLOAD_COUNT_PREFIX + PrincipalUtil.getId(), between, TimeUnit.SECONDS);
    }

    /**
     * 小票审核
     */
    @Transactional
    public void audit(int receiptId, ReceiptAuditReq req) {
        Receipt receipt = this.getById(receiptId);
        Assert.notNull(receipt, "小票不存在");
        Assert.isTrue(receipt.getAuditStatus() == EAudit.AUDITING.getValue(), "小票不是审核中状态");

        if (Objects.equals(req.getAuditStatus(), EAudit.AUDITED.getValue())) {
            // 保存流水
            creditFlowService.save(new CreditFlow()
                    .setUserId(receipt.getUserId())
                    .setBusinessId(receiptId)
                    .setBusinessType(EFlowBusinessType.UPLOAD_RECEIPT.getValue())
                    .setFlowType(EFlowType.OBTAIN.getValue())
                    .setCredit(req.getCredit()));
            // 增加用户积分
            userMapper.addCredit(receipt.getUserId(), req.getCredit());
        }

        receipt.setCredit(req.getCredit());
        receipt.setAuditStatus(req.getAuditStatus());
        receipt.setReason(req.getReason());
        receipt.setAuditTime(LocalDateTime.now());
        receipt.setAuditId(PrincipalUtil.getId());
        this.updateById(receipt);
    }
}
